# - Run cppcheck on c++ source files as a custom target and a test
#
#  include(CppcheckTargets)
#  add_cppcheck(<target-name> [UNUSED_FUNCTIONS] [STYLE] [POSSIBLE_ERROR] [FAIL_ON_WARNINGS]) -
#    Create a target to check a target's sources with cppcheck and the indicated options
#  add_cppcheck_sources(<target-name> [UNUSED_FUNCTIONS] [STYLE] [POSSIBLE_ERROR] [FAIL_ON_WARNINGS]) -
#    Create a target to check standalone sources with cppcheck and the indicated options
#
# Requires these CMake modules:
#  Findcppcheck
#
# Requires CMake 2.6 or newer (uses the 'function' command)
#
# Original Author:
# 2009-2010 Ryan Pavlik <rpavlik@iastate.edu> <abiryan@ryand.net>
# http://academic.cleardefinition.com
# Iowa State University HCI Graduate Program/VRAC
#
# Copyright Iowa State University 2009-2010.
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE_1_0.txt or copy at
# http://www.boost.org/LICENSE_1_0.txt)

if(__add_cppcheck)
        return()
endif()
set(__add_cppcheck YES)

if(NOT CPPCHECK_FOUND)
        find_package(cppcheck QUIET)
endif()

if(CPPCHECK_FOUND)
        if(NOT TARGET all_cppcheck)
                add_custom_target(all_cppcheck)
                set_target_properties(all_cppcheck PROPERTIES EXCLUDE_FROM_ALL TRUE)
        endif()
endif()

function(add_cppcheck_sources _targetname)
        if(CPPCHECK_FOUND)
                set(_cppcheck_args)
                set(_input ${ARGN})
                list(FIND _input UNUSED_FUNCTIONS _unused_func)
                if("${_unused_func}" GREATER "-1")
                        list(APPEND _cppcheck_args ${CPPCHECK_UNUSEDFUNC_ARG})
                        list(REMOVE_AT _input ${_unused_func})
                endif()

                list(FIND _input STYLE _style)
                if("${_style}" GREATER "-1")
                        list(APPEND _cppcheck_args ${CPPCHECK_STYLE_ARG})
                        list(REMOVE_AT _input ${_style})
                endif()

                list(FIND _input POSSIBLE_ERROR _poss_err)
                if("${_poss_err}" GREATER "-1")
                        list(APPEND _cppcheck_args ${CPPCHECK_POSSIBLEERROR_ARG})
                        list(REMOVE_AT _input ${_poss_err})
                endif()

                list(FIND _input FAIL_ON_WARNINGS _fail_on_warn)
                if("${_fail_on_warn}" GREATER "-1")
                        list(APPEND
                                CPPCHECK_FAIL_REGULAR_EXPRESSION
                                ${CPPCHECK_WARN_REGULAR_EXPRESSION})
                        list(REMOVE_AT _input ${_fail_on_warn})
                endif()

                set(_files)
                foreach(_source ${_input})
                        get_source_file_property(_cppcheck_loc "${_source}" LOCATION)
                        if(_cppcheck_loc)
                                # This file has a source file property, carry on.
                                get_source_file_property(_cppcheck_lang "${_source}" LANGUAGE)
                                if("${_cppcheck_lang}" MATCHES "CXX")
                                        list(APPEND _files "${_cppcheck_loc}")
                                endif()
                        else()
                                # This file doesn't have source file properties - figure it out.
                                get_filename_component(_cppcheck_loc "${_source}" ABSOLUTE)
                                if(EXISTS "${_cppcheck_loc}")
                                        list(APPEND _files "${_cppcheck_loc}")
                                else()
                                        message(FATAL_ERROR
                                                "Adding CPPCHECK for file target ${_targetname}: "
                                                "File ${_source} does not exist or needs a corrected path location "
                                                "since we think its absolute path is ${_cppcheck_loc}")
                                endif()
                        endif()
                endforeach()

                if("1.${CMAKE_VERSION}" VERSION_LESS "1.2.8.0")
                        # Older than CMake 2.8.0
                        add_test(${_targetname}_cppcheck_test
                                "${CPPCHECK_EXECUTABLE}"
                                ${CPPCHECK_TEMPLATE_ARG}
                                ${_cppcheck_args}
                                ${_files})
                else()
                        # CMake 2.8.0 and newer
                        add_test(NAME
                                ${_targetname}_cppcheck_test
                                COMMAND
                                "${CPPCHECK_EXECUTABLE}"
                                ${CPPCHECK_TEMPLATE_ARG}
                                ${_cppcheck_args}
                                ${_files})
                endif()

                set_tests_properties(${_targetname}_cppcheck_test
                        PROPERTIES
                        FAIL_REGULAR_EXPRESSION
                        "${CPPCHECK_FAIL_REGULAR_EXPRESSION}")

                add_custom_command(TARGET
                        all_cppcheck
                        PRE_BUILD
                        COMMAND
                        ${CPPCHECK_EXECUTABLE}
                        ${CPPCHECK_QUIET_ARG}
                        ${CPPCHECK_TEMPLATE_ARG}
                        ${_cppcheck_args}
                        ${_files}
                        WORKING_DIRECTORY
                        "${CMAKE_CURRENT_SOURCE_DIR}"
                        COMMENT
                        "${_targetname}_cppcheck: Running cppcheck on target ${_targetname}..."
                        VERBATIM)
        endif()
endfunction()

function(add_cppcheck _name)
        if(NOT TARGET ${_name})
                message(FATAL_ERROR
                        "add_cppcheck given a target name that does not exist: '${_name}' !")
        endif()
        if(CPPCHECK_FOUND)
                set(_cppcheck_args)

                list(FIND ARGN UNUSED_FUNCTIONS _unused_func)
                if("${_unused_func}" GREATER "-1")
                        list(APPEND _cppcheck_args ${CPPCHECK_UNUSEDFUNC_ARG})
                endif()

                list(FIND ARGN STYLE _style)
                if("${_style}" GREATER "-1")
                        list(APPEND _cppcheck_args ${CPPCHECK_STYLE_ARG})
                endif()

                list(FIND ARGN POSSIBLE_ERROR _poss_err)
                if("${_poss_err}" GREATER "-1")
                        list(APPEND _cppcheck_args ${CPPCHECK_POSSIBLEERROR_ARG})
                endif()

                list(FIND _input FAIL_ON_WARNINGS _fail_on_warn)
                if("${_fail_on_warn}" GREATER "-1")
                        list(APPEND
                                CPPCHECK_FAIL_REGULAR_EXPRESSION
                                ${CPPCHECK_WARN_REGULAR_EXPRESSION})
                        list(REMOVE_AT _input ${_unused_func})
                endif()

                get_target_property(_cppcheck_sources "${_name}" SOURCES)
                set(_files)
                foreach(_source ${_cppcheck_sources})
                        get_source_file_property(_cppcheck_lang "${_source}" LANGUAGE)
                        get_source_file_property(_cppcheck_loc "${_source}" LOCATION)
                        if("${_cppcheck_lang}" MATCHES "CXX")
                                list(APPEND _files "${_cppcheck_loc}")
                        endif()
                endforeach()

                if("1.${CMAKE_VERSION}" VERSION_LESS "1.2.8.0")
                        # Older than CMake 2.8.0
                        add_test(${_name}_cppcheck_test
                                "${CPPCHECK_EXECUTABLE}"
                                ${CPPCHECK_TEMPLATE_ARG}
                                ${_cppcheck_args}
                                ${_files})
                else()
                        # CMake 2.8.0 and newer
                        add_test(NAME
                                ${_name}_cppcheck_test
                                COMMAND
                                "${CPPCHECK_EXECUTABLE}"
                                ${CPPCHECK_TEMPLATE_ARG}
                                ${_cppcheck_args}
                                ${_files})
                endif()

                set_tests_properties(${_name}_cppcheck_test
                        PROPERTIES
                        FAIL_REGULAR_EXPRESSION
                        "${CPPCHECK_FAIL_REGULAR_EXPRESSION}")

                add_custom_command(TARGET
                        all_cppcheck
                        PRE_BUILD
                        COMMAND
                        ${CPPCHECK_EXECUTABLE}
                        ${CPPCHECK_QUIET_ARG}
                        ${CPPCHECK_TEMPLATE_ARG}
                        ${_cppcheck_args}
                        ${_files}
                        WORKING_DIRECTORY
                        "${CMAKE_CURRENT_SOURCE_DIR}"
                        COMMENT
                        "${_name}_cppcheck: Running cppcheck on target ${_name}..."
                        VERBATIM)
        endif()

endfunction()
